package cn.demoncat.util.lang;

import cn.demoncat.util.exception.ParamRuntimeException;

import java.time.LocalDateTime;
import java.util.*;

/**
 * Quartz-Cron表达式工具
 * <pre>
 * 格式：秒 分 时 日 月 周 [年]
 * ————————————————————————————————————————————————————————————
 * 秒：0-59，*
 * 分：0-59，*
 * 时：0-23，*
 * 日：1-31，*，?
 * 月：1-12，*，JAN-FEB-MAR-APR-MAY-JUN-JUL-AUG-SEP-OCT-NOV-DEC
 * 周：1-7，*，?，MON-TUE-WED-THU-FRI-SAT-SUN
 * ————————————————————————————————————————————————————————————
 * 特殊符号：
 * * 匹配所有的值，表示每个TimeUnit执行。
 * ? 由day-of-month和day-of-week之一指定，表示非明确值，来避免冲突
 * - 指定范围。如1-3表示1秒到3秒每秒执行。
 * , 指定多个。如1,2,3表示1秒2秒3秒时执行。
 * / 指定增量。如0/15表示从0秒开始每15秒执行1次。
 * L 在day-of-month表示月末，在day-of-week表示周末。如2L在day-of-month表示月的倒数第2天。
 * W 在day-of-month表示最近的工作日。
 * ————————————————————————————————————————————————————————————
 * </pre>
 * @author 延晓磊
 *
 * @since 2018年7月14日
 */
public class QuartzCronUtil {
 
	/**
	 * 校验CRON表达式
	 * 	
	 * @param expression	CRON表达式：秒 分 时 日 月 周 [年]
	 * 
	 * @throws ParamRuntimeException 表达式错误 
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年7月14日
	 */
	public static void validate(String expression) {
		build(expression);
	}

	/**
	 * 校验是否为CRON表达式
	 * 
	 * @param expression	CRON表达式：秒 分 时 日 月 周 [年]
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年7月14日
	 */
	public static boolean isValidate(String expression) {
		try {
			validate(expression);
		} catch (Exception e) {
			return false;
		}
		return true;
	}

	/**
	 * 解析CRON表达式
	 * 
	 * @param expression	CRON表达式：秒 分 时 日 月 周 [年]
	 * @return 表达式对象
	 *
	 * @author 延晓磊
	 *
	 * @since 2018年7月14日
	 */
	public static CronExpression build(String expression) {
		// 非空校验
		ParamEmptyUtil.checkBlank(expression);
		// 转大写
		expression = expression.toUpperCase(Locale.US);
		// 创建表达式
		CronExpression cron = new CronExpression();
		cron.expression = expression;
		return setExpression(cron);
	}

	/**
	 * 表达式
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年7月14日
	 */
	public static class CronExpression {
		/**
		 * CRON表达式：秒 分 时 日 月 周 [年]
		 */
		public String expression;
		public TreeSet<Integer> seconds = new TreeSet<>();
		public TreeSet<Integer> minutes = new TreeSet<>();
		public TreeSet<Integer> hours = new TreeSet<>();
		public TreeSet<Integer> daysOfMonth = new TreeSet<>();
		public TreeSet<Integer> months = new TreeSet<>();
		public TreeSet<Integer> daysOfWeek = new TreeSet<>();
		public TreeSet<Integer> years = new TreeSet<>();
		public boolean lastDayOfWeek = false;
		public int nthDayOfWeek = 0;
		public boolean lastDayOfMonth = false;
		public boolean nearestWeekday = false;
		public int lastDayOffset = 0;

		@Override
		public String toString() {
			return expression;
		}

		/**
		 * 获取下次执行时间
		 *
		 * @param date	基准日期，默认当前
		 * @return
		 *
		 * @author 延晓磊
		 * @since 2022年03月30日
		 */
		public LocalDateTime getNextTime(LocalDateTime date) {
			if(date == null){
				date = LocalDateTime.now();
			}
			// 推后1秒
			date = date.plusSeconds(1).withNano(0);
			// 循环获取下一次时间
			SortedSet<Integer> st;
			int t,sec,min,hour,day,week,mon,year,lastDay,tMon,tWeek,daysToAdd,weekOfMonth;
			boolean dayShifted;
			boolean isDayOfMonth = !daysOfMonth.contains(NO_SPEC);
			boolean isDayOfWeek = !daysOfWeek.contains(NO_SPEC);
			LocalDateTime tDate;
			while (true) {
				// 防止无限循环
				if (date.getYear() > MAX_YEAR) {
					return null;
				}

				// 秒
				sec = date.getSecond();
				st = seconds.tailSet(sec);
				if (st.isEmpty()) {
					sec = seconds.first();
					date = date.plusMinutes(1);
				}else{
					sec = st.first();
				}
			 	date = date.withSecond(sec);

				// 分
				t = -1;
				min = date.getMinute();
				st = minutes.tailSet(min);
				if (st.isEmpty()) {
					min = minutes.first();
					date = date.plusHours(1);
				}else{
					t = min;
					min = st.first();
				}
				if (min != t) {
					date = date.withSecond(0).withMinute(min);
					continue;
				}
				date = date.withMinute(min);

				// 时
				t = -1;
				hour = date.getHour();
				st = hours.tailSet(hour);
 				if (st.isEmpty()) {
					hour = hours.first();
					date = date.plusDays(1);
 				}else{
					t = hour;
					hour = st.first();
				}
				if (hour != t) {
					date = date.withSecond(0).withMinute(0).withHour(hour);
					continue;
				}
				date = date.withHour(hour);

				// 日
				t = -1;
				day = date.getDayOfMonth();
				mon = date.getMonthValue();
				tMon = mon;
				if (isDayOfMonth && !isDayOfWeek) {
					// day of month
					st = daysOfMonth.tailSet(day);
					if (lastDayOfMonth) {
						// 月末
						t = day;
						lastDay = date.toLocalDate().lengthOfMonth();
						day = lastDay - lastDayOffset;
						if(nearestWeekday) {
							// 工作日
							week = date.getDayOfWeek().getValue();
							tDate = LocalDateTime.of(date.getYear(), mon, day, hour, min, sec);
							if(week == 6 && day == 1) {
								day += 2;
							} else if(week == 6) {
								day -= 1;
							} else if(week == 7 && day == lastDay) {
								day -= 2;
							} else if(week == 7) {
								day += 1;
							}
							tDate = tDate.withDayOfMonth(day);
							if(tDate.isBefore(date)) {
								day = 1;
								date = date.plusMonths(1);
							}
						} else {
							if(t > day) {
								mon ++;
								if(mon > 12) {
									tMon = -1;
									date = date.withMonth(1).plusYears(1);
								}
								day = 1;
							}
						}
					} else if(nearestWeekday) {
						// 工作日
						t = day;
						day = daysOfMonth.first();
						tDate = LocalDateTime.of(date.getYear(), mon, day, hour, min, sec);
						lastDay = date.toLocalDate().lengthOfMonth();
						week = date.getDayOfWeek().getValue();
						if(week == 6 && day == 1) {
							day += 2;
						} else if(week == 6) {
							day -= 1;
						} else if(week == 7 && day == lastDay) {
							day -= 2;
						} else if(week == 7) {
							day += 1;
						}
						tDate = tDate.withDayOfMonth(day);
						if(tDate.isBefore(date)) {
							day = daysOfMonth.first();
							date = date.plusMonths(1);
						}
					} else if (!st.isEmpty()) {
						t = day;
						day = st.first();
						lastDay = date.toLocalDate().lengthOfMonth();
						if (day > lastDay) {
							day = daysOfMonth.first();
							date = date.plusMonths(1);
						}
					} else {
						day = daysOfMonth.first();
						date = date.plusMonths(1);
					}
					mon = date.getMonthValue();
					if (day != t || mon != tMon) {
						date = date.withSecond(0).withMinute(0).withHour(0).withDayOfMonth(day);
						continue;
					}
				} else if (isDayOfWeek && !isDayOfMonth) {
					// day of week
					if (lastDayOfWeek) {
						tWeek = daysOfWeek.first();
						week = date.getDayOfWeek().getValue();
						daysToAdd = 0;
						if (week < tWeek) {
							daysToAdd = tWeek - week;
						}
						if (week > tWeek) {
							daysToAdd = tWeek + (7 - week);
						}
						lastDay = date.toLocalDate().lengthOfMonth();
						if (day + daysToAdd > lastDay) {
							date = date.withSecond(0).withMinute(0).withHour(0).withDayOfMonth(1).plusMonths(1);
							continue;
						}
						while ((day + daysToAdd + 7) <= lastDay) {
							daysToAdd += 7;
						}
						day += daysToAdd;
						if (daysToAdd > 0) {
							date = date.withSecond(0).withMinute(0).withHour(0).withDayOfMonth(day);
							continue;
						}
					} else if (nthDayOfWeek != 0) {
						// 指定天
						tWeek = daysOfWeek.first();
						week = date.getDayOfWeek().getValue();
						daysToAdd = 0;
						if (week < tWeek) {
							daysToAdd = tWeek - week;
						} else if (week > tWeek) {
							daysToAdd = tWeek + (7 - week);
						}
						dayShifted = daysToAdd > 0;
						day += daysToAdd;
						weekOfMonth = day / 7;
						if (day % 7 > 0) {
							weekOfMonth ++;
						}
						daysToAdd = (nthDayOfWeek - weekOfMonth) * 7;
						day += daysToAdd;
						if (daysToAdd < 0 || day > date.toLocalDate().lengthOfMonth()) {
							date = date.withSecond(0).withMinute(0).withHour(0).withDayOfMonth(1).plusMonths(1);
							continue;
						} else if (daysToAdd > 0 || dayShifted) {
							date = date.withSecond(0).withMinute(0).withHour(0).withDayOfMonth(day);
							continue;
						}
					} else {
						tWeek = daysOfWeek.first();
						week = date.getDayOfWeek().getValue();
						st = daysOfWeek.tailSet(week);
						if (!st.isEmpty()) {
							tWeek = st.first();
						}
						daysToAdd = 0;
						if (week < tWeek) {
							daysToAdd = tWeek - week;
						}
						if (week > tWeek) {
							daysToAdd = tWeek + (7 - week);
						}
						lastDay = date.toLocalDate().lengthOfMonth();
						if (day + daysToAdd > lastDay) {
							date = date.withSecond(0).withMinute(0).withHour(0).withDayOfMonth(1).plusMonths(1);
							continue;
						} else if (daysToAdd > 0) {
							date = date.withSecond(0).withMinute(0).withHour(0).withDayOfMonth(day + daysToAdd);
							continue;
						}
					}
				} else {
					throw buildException("不能同时出现Day-of-Month和Day-of-Week");
				}
				date = date.withDayOfMonth(day);

				// 月
				t = -1;
				mon = date.getMonthValue();
				st = months.tailSet(mon);
		  		if (st.isEmpty()) {
					mon = months.first();
					date = date.plusYears(1);
		  		}else{
					t = mon;
					mon = st.first();
				}
				if (mon != t) {
					date = date.withSecond(0).withMinute(0).withHour(0).withDayOfMonth(1).withMonth(mon);
					continue;
				}
				date = date.withMonth(mon);

				// 年
				year = date.getYear();
				st = years.tailSet(year);
				if (st.isEmpty()) {
					return null;
				}else{
					t = year;
					year = st.first();
				}
				if (year != t) {
					date = date.withSecond(0).withMinute(0).withHour(0).withDayOfMonth(1).withMonth(0).withYear(year);
					continue;
				}
				date = date.withYear(year);
				return date;
			}
		}
	}

	/**
	 * 值集
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年7月14日
	 */
	public static class ValueSet {
		public int value;
		public int pos;
	}

	/* ====================================== 构建CRON表达式 ====================================== */

	// 表达式组件的序号
	private static final int SECOND = 0;
	private static final int MINUTE = 1;
	private static final int HOUR = 2;
	private static final int DAY_OF_MONTH = 3;
	private static final int MONTH = 4;
	private static final int DAY_OF_WEEK = 5;
	private static final int YEAR = 6;

	// 特殊符号 *
	private static final int ALL_SPEC_INT = 99;
	// 特殊符号 ?
	private static final int NO_SPEC_INT = 98;
	private static final Integer ALL_SPEC = ALL_SPEC_INT;
	private static final Integer NO_SPEC = NO_SPEC_INT;

	// 月
	private static final Map<String, Integer> monthMap = new HashMap<String, Integer>() {
		private static final long serialVersionUID = 1L;
		{
			put("JAN", 0);
			put("FEB", 1);
			put("MAR", 2);
			put("APR", 3);
			put("MAY", 4);
			put("JUN", 5);
			put("JUL", 6);
			put("AUG", 7);
			put("SEP", 8);
			put("OCT", 9);
			put("NOV", 10);
			put("DEC", 11);
		}
	};
	// 周
	private static final Map<String, Integer> dayMap = new HashMap<String, Integer>() {
		private static final long serialVersionUID = 1L;
		{
			put("MON", 1);
			put("TUE", 2);
			put("WED", 3);
			put("THU", 4);
			put("FRI", 5);
			put("SAT", 6);
			put("SUN", 7);
		}
	};

	// 年
	private static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;

	/**
	 * 设置CRON表达式
	 *
	 * @param cron
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2018年7月14日
	 */
	private static CronExpression setExpression(CronExpression cron)  {
		int exprOn = SECOND;
		// 分隔空格
		StringTokenizer exprTok = new StringTokenizer(cron.expression , " \t", false);
		String expr;
		StringTokenizer vTok;
		String v;
		while (exprTok.hasMoreTokens() && exprOn <= YEAR) {
			expr = exprTok.nextToken().trim();
			if (exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
				throw buildException("'L' 和 'LW' 不能用于Day-of-Month");
			}
			if (exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
				throw buildException("'L' 和 'LW' 不能用于Day-of-Week");
			}
			if (exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') + 1) != -1) {
				throw buildException("不支持多个Nth Day-of-Week");
			}
			vTok = new StringTokenizer(expr, ",");
			while (vTok.hasMoreTokens()) {
				v = vTok.nextToken();
				storeExpressionValues(cron, 0, v, exprOn);
			}
			exprOn++;
		}

		if (exprOn <= DAY_OF_WEEK) {
			throw buildException("格式应为【秒 分 时 日 月 周 [年]】");
		}

		if (exprOn <= YEAR) {
			storeExpressionValues(cron, 0, "*", YEAR);
		}

		TreeSet<Integer> dow = getSet(cron, DAY_OF_WEEK);
		TreeSet<Integer> dom = getSet(cron, DAY_OF_MONTH);

		boolean dayOfMSpec = !dom.contains(NO_SPEC);
		boolean dayOfWSpec = !dow.contains(NO_SPEC);

		if (!dayOfMSpec || dayOfWSpec) {
			if (!dayOfWSpec || dayOfMSpec) {
				throw buildException("不能同时出现Day-of-Month和Day-of-Week");
			}
		}
		return cron;
	}

	/**
	 * 保存表达式的值
	 *
	 * @author 延晓磊
	 *
	 * @since 2018年7月14日
	 */
	private static int storeExpressionValues(CronExpression cron, int pos, String s, int type) {
		int incr = 0;
		int i = skipWhiteSpace(pos, s);
		if (i >= s.length()) {
			return i;
		}
		char c = s.charAt(i);
		if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) {
			String sub = s.substring(i, i + 3);
			int sval;
			int eval = -1;
			if (type == MONTH) {
				sval = getMonthNumber(sub) + 1;
				if (sval <= 0) {
					throw buildException("无效的Month值", sub);
				}
				if (s.length() > i + 3) {
					c = s.charAt(i + 3);
					if (c == '-') {
						i += 4;
						sub = s.substring(i, i + 3);
						eval = getMonthNumber(sub) + 1;
						if (eval <= 0) {
							throw buildException("无效的Month值", sub);
						}
					}
				}
			} else if (type == DAY_OF_WEEK) {
				sval = getDayOfWeekNumber(sub);
				if (sval < 0) {
					throw buildException("无效的Day-of-Week值", sub);
				}
				if (s.length() > i + 3) {
					c = s.charAt(i + 3);
					if (c == '-') {
						i += 4;
						sub = s.substring(i, i + 3);
						eval = getDayOfWeekNumber(sub);
						if (eval < 0) {
							throw buildException("无效的Day-of-Week值", sub);
						}
					} else if (c == '#') {
						try {
							i += 4;
							cron.nthDayOfWeek = Integer.parseInt(s.substring(i));
							if (cron.nthDayOfWeek < 1 || cron.nthDayOfWeek > 5) {
								throw buildException("指定的DayOfWeek无效", cron.nthDayOfWeek);
							}
						} catch (Exception e) {
							throw buildException("1和5之间的数值遵守 '#' 选项");
						}
					} else if (c == 'L') {
						cron.lastDayOfWeek = true;
						i++;
					}
				}

			} else {
				throw buildException("包含无效的字符", sub);
			}
			if (eval != -1) {
				incr = 1;
			}
			addToSet(cron, sval, eval, incr, type);
			return (i + 3);
		}

		if (c == '?') {
			i++;
			if ((i + 1) < s.length() && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
				throw buildException("无效的字符在 '?' 后面", s.charAt(i));
			}
			if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
				throw buildException("'?' 只能用于 Day-of-Month 和 Day-of-Week");
			}
			if (type == DAY_OF_WEEK && !cron.lastDayOfMonth) {
				int val = cron.daysOfMonth.last();
				if (val == NO_SPEC_INT) {
					throw buildException("'?' 只能用于 Day-of-Month 和 Day-of-Week");
				}
			}
			addToSet(cron, NO_SPEC_INT, -1, 0, type);
			return i;
		}

		if (c == '*' || c == '/') {
			if (c == '*' && (i + 1) >= s.length()) {
				addToSet(cron, ALL_SPEC_INT, -1, incr, type);
				return i + 1;
			} else if (c == '/' && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t')) {
				throw buildException("'/' 必须和整数一起使用");
			} else if (c == '*') {
				i++;
			}
			c = s.charAt(i);
			if (c == '/') {
				i++;
				if (i >= s.length()) {
					throw buildException("包含无效的字符", c);
				}

				incr = getNumericValue(s, i);

				i++;
				if (incr > 10) {
					i++;
				}
				checkIncrementRange(incr, type);
			} else {
				incr = 1;
			}

			addToSet(cron, ALL_SPEC_INT, -1, incr, type);
			return i;
		} else if (c == 'L') {
			i++;
			if (type == DAY_OF_MONTH) {
				cron.lastDayOfMonth = true;
			}
			if (type == DAY_OF_WEEK) {
				addToSet(cron, 7, 7, 0, type);
			}
			if (type == DAY_OF_MONTH && s.length() > i) {
				c = s.charAt(i);
				if (c == '-') {
					ValueSet vs = getValue(0, s, i + 1);
					cron.lastDayOffset = vs.value;
					if (cron.lastDayOffset > 30)
						throw buildException("Day-of-Month 必须 <= 30");
					i = vs.pos;
				}
				if (s.length() > i) {
					c = s.charAt(i);
					if (c == 'W') {
						cron.nearestWeekday = true;
						i++;
					}
				}
			}
			return i;
		} else if (c >= '0' && c <= '9') {
			int val = Integer.parseInt(String.valueOf(c));
			i++;
			if (i >= s.length()) {
				addToSet(cron, val, -1, -1, type);
			} else {
				c = s.charAt(i);
				if (c >= '0' && c <= '9') {
					ValueSet vs = getValue(val, s, i);
					val = vs.value;
					i = vs.pos;
				}
				i = checkNext(cron, i, s, val, type);
				return i;
			}
		} else {
			throw buildException("包含无效的字符", c);
		}
		return i;
	}

	/**
	 * 检查范围
	 *
	 * @author 延晓磊
	 *
	 * @since 2018年7月14日
	 */
	private static void checkIncrementRange(int incr, int type) {
		if (incr > 59 && (type == SECOND || type == MINUTE)) {
			throw buildException("Increment > 60 : " + incr);
		} else if (incr > 23 && (type == HOUR)) {
			throw buildException("Increment > 24 : " + incr);
		} else if (incr > 31 && (type == DAY_OF_MONTH)) {
			throw buildException("Increment > 31 : " + incr);
		} else if (incr > 7 && (type == DAY_OF_WEEK)) {
			throw buildException("Increment > 7 : " + incr);
		} else if (incr > 12 && (type == MONTH)) {
			throw buildException("Increment > 12 : " + incr);
		}
	}

	/**
	 * 检查
	 *
	 * @author 延晓磊
	 *
	 * @since 2018年7月14日
	 */
	private static int checkNext(CronExpression cron, int pos, String s, int val, int type) {
		int end = -1;
		int i = pos;

		if (i >= s.length()) {
			addToSet(cron, val, end, -1, type);
			return i;
		}

		char c = s.charAt(pos);

		if (c == 'L') {
			if (type == DAY_OF_WEEK) {
				if (val < 1 || val > 7)
					throw buildException("Day-of-Week 的值必须是 1 - 7");
				cron.lastDayOfWeek = true;
			} else {
				throw buildException("'L' 不能用在此处 (pos=" + i + ")");
			}
			TreeSet<Integer> set = getSet(cron, type);
			set.add(val);
			i++;
			return i;
		}

		if (c == 'W') {
			if (type == DAY_OF_MONTH) {
				cron.nearestWeekday = true;
			} else {
				throw buildException("'W' 不能用在此处. (pos=" + i + ")");
			}
			if (val > 31)
				throw buildException("'W' 对于大于 31 的值没有意义");
			TreeSet<Integer> set = getSet(cron, type);
			set.add(val);
			i++;
			return i;
		}

		if (c == '#') {
			if (type != DAY_OF_WEEK) {
				throw buildException("'#' 不能用在此处 (pos=" + i + ")");
			}
			i++;
			try {
				cron.nthDayOfWeek = Integer.parseInt(s.substring(i));
				if (cron.nthDayOfWeek < 1 || cron.nthDayOfWeek > 5) {
					throw buildException("指定的DayOfWeek无效", cron.nthDayOfWeek);
				}
			} catch (Exception e) {
				throw buildException("1和5之间的数值遵守 '#' 选项");
			}

			TreeSet<Integer> set = getSet(cron, type);
			set.add(val);
			i++;
			return i;
		}

		if (c == '-') {
			i++;
			c = s.charAt(i);
			int v = Integer.parseInt(String.valueOf(c));
			end = v;
			i++;
			if (i >= s.length()) {
				addToSet(cron, val, end, 1, type);
				return i;
			}
			c = s.charAt(i);
			if (c >= '0' && c <= '9') {
				ValueSet vs = getValue(v, s, i);
				end = vs.value;
				i = vs.pos;
			}
			if (i < s.length() && ((c = s.charAt(i)) == '/')) {
				i++;
				int v2 = Integer.parseInt(String.valueOf(c));
				i++;
				if (i >= s.length()) {
					addToSet(cron, val, end, v2, type);
					return i;
				}
				c = s.charAt(i);
				if (c >= '0' && c <= '9') {
					ValueSet vs = getValue(v2, s, i);
					int v3 = vs.value;
					addToSet(cron, val, end, v3, type);
					i = vs.pos;
				} else {
					addToSet(cron, val, end, v2, type);
				}
			} else {
				addToSet(cron, val, end, 1, type);
			}
			return i;
		}

		if (c == '/') {
			if ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t') {
				throw buildException("'/' 必须和整数一起使用");
			}

			i++;
			c = s.charAt(i);
			int v2 = Integer.parseInt(String.valueOf(c));
			i++;
			if (i >= s.length()) {
				checkIncrementRange(v2, type);
				addToSet(cron, val, end, v2, type);
				return i;
			}
			c = s.charAt(i);
			if (c >= '0' && c <= '9') {
				ValueSet vs = getValue(v2, s, i);
				int v3 = vs.value;
				checkIncrementRange(v3, type);
				addToSet(cron, val, end, v3, type);
				i = vs.pos;
				return i;
			} else {
				throw buildException("无效的字符 '" + c + "' 在 '/' 后面");
			}
		}

		addToSet(cron, val, end, 0, type);
		i++;
		return i;
	}

	/**
	 * 跳过空格
	 *
	 * @author 延晓磊
	 *
	 * @since 2018年7月14日
	 */
	private static int skipWhiteSpace(int i, String s) {
		for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) {
			//跳过
		}
		return i;
	}

	/**
	 * 查找空格
	 *
	 * @author 延晓磊
	 *
	 * @since 2018年7月14日
	 */
	private static int findNextWhiteSpace(int i, String s) {
		for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) {
			//跳过
		}
		return i;
	}

	/**
	 * 添加值
	 *
	 * @author 延晓磊
	 *
	 * @since 2018年7月14日
	 */
	private static void addToSet(CronExpression cron, int val, int end, int incr, int type) {
		TreeSet<Integer> set = getSet(cron, type);
		if (type == SECOND) {
			if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
				throw buildException("Second 的值必须是 0 - 59");
			}
		}else if (type == MINUTE) {
			if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
				throw buildException("Minute 的值必须是 0 - 59");
			}
		} else if (type == HOUR) {
			if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
				throw buildException("Hour 的值必须是 0 - 23");
			}
		} else if (type == DAY_OF_MONTH) {
			if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT) && (val != NO_SPEC_INT)) {
				throw buildException("Day-of-Month 的值必须是 1 - 31");
			}
		} else if (type == MONTH) {
			if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
				throw buildException("Month 的值必须是 1 - 12");
			}
		} else if (type == DAY_OF_WEEK) {
			if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT) && (val != NO_SPEC_INT)) {
				throw buildException("Day-of-Week 的值必须是 1 - 7");
			}
		}
		if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
			if (val != -1) {
				set.add(val);
			} else {
				set.add(NO_SPEC);
			}
			return;
		}

		int startAt = val;
		int stopAt = end;

		if (val == ALL_SPEC_INT && incr <= 0) {
			incr = 1;
			set.add(ALL_SPEC);
		}

		if (type == SECOND || type == MINUTE) {
			if (stopAt == -1) {
				stopAt = 59;
			}
			if (startAt == ALL_SPEC_INT) {
				startAt = 0;
			}
		} else if (type == HOUR) {
			if (stopAt == -1) {
				stopAt = 23;
			}
			if (startAt == ALL_SPEC_INT) {
				startAt = 0;
			}
		} else if (type == DAY_OF_MONTH) {
			if (stopAt == -1) {
				stopAt = 31;
			}
			if (startAt == ALL_SPEC_INT) {
				startAt = 1;
			}
		} else if (type == MONTH) {
			if (stopAt == -1) {
				stopAt = 12;
			}
			if (startAt == ALL_SPEC_INT) {
				startAt = 1;
			}
		} else if (type == DAY_OF_WEEK) {
			if (stopAt == -1) {
				stopAt = 7;
			}
			if (startAt == -1 || startAt == ALL_SPEC_INT) {
				startAt = 1;
			}
		} else if (type == YEAR) {
			if (stopAt == -1) {
				stopAt = MAX_YEAR;
			}
			if (startAt == -1 || startAt == ALL_SPEC_INT) {
				startAt = 1970;
			}
		}

		int max = -1;
		if (stopAt < startAt) {
			switch (type) {
				case SECOND:
				case MINUTE:
					max = 60;
					break;
				case HOUR:
					max = 24;
					break;
				case MONTH:
					max = 12;
					break;
				case DAY_OF_WEEK:
					max = 7;
					break;
				case DAY_OF_MONTH:
					max = 31;
					break;
				case YEAR:
					throw buildException("Start year must be less than stop year");
				default:
					throw buildException("类型错误", type);
			}
			stopAt += max;
		}

		for (int i = startAt; i <= stopAt; i += incr) {
			if (max == -1) {
				set.add(i);
			} else {
				int i2 = i % max;
				if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH)) {
					i2 = max;
				}

				set.add(i2);
			}
		}
	}

	/**
	 * 获取值
	 *
	 * @author 延晓磊
	 *
	 * @since 2018年7月14日
	 */
	private static TreeSet<Integer> getSet(CronExpression cron, int type) {
		switch (type) {
			case SECOND:
				return cron.seconds;
			case MINUTE:
				return cron.minutes;
			case HOUR:
				return cron.hours;
			case DAY_OF_MONTH:
				return cron.daysOfMonth;
			case MONTH:
				return cron.months;
			case DAY_OF_WEEK:
				return cron.daysOfWeek;
			case YEAR:
				return cron.years;
			default:
				throw buildException("类型错误", type);
		}
	}

	/**
	 * 获取值集
	 *
	 * @author 延晓磊
	 *
	 * @since 2018年7月14日
	 */
	private static ValueSet getValue(int v, String s, int i) {
		char c = s.charAt(i);
		StringBuilder s1 = new StringBuilder(String.valueOf(v));
		while (c >= '0' && c <= '9') {
			s1.append(c);
			i++;
			if (i >= s.length()) {
				break;
			}
			c = s.charAt(i);
		}
		ValueSet val = new ValueSet();

		val.pos = (i < s.length()) ? i : i + 1;
		val.value = Integer.parseInt(s1.toString());
		return val;
	}

	/**
	 * 获取数值
	 *
	 * @author 延晓磊
	 *
	 * @since 2018年7月14日
	 */
	private static int getNumericValue(String s, int i) {
		int endOfVal = findNextWhiteSpace(i, s);
		String val = s.substring(i, endOfVal);
		return Integer.parseInt(val);
	}

	/**
	 * 获取月值
	 *
	 * @author 延晓磊
	 *
	 * @since 2018年7月14日
	 */
	private static int getMonthNumber(String s) {
		Integer integer = monthMap.get(s);
		if (integer == null) {
			return -1;
		}
		return integer;
	}

	/**
	 * 获取周值
	 *
	 * @author 延晓磊
	 *
	 * @since 2018年7月14日
	 */
	private static int getDayOfWeekNumber(String s) {
		Integer integer = dayMap.get(s);
		if (integer == null) {
			return -1;
		}
		return integer;
	}

	/**
	 * 异常
	 *
	 * @param msg
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2018年7月14日
	 */
	private static ParamRuntimeException buildException(String msg){
		return new ParamRuntimeException("Cron表达式错误：" + msg);
	}

	/**
	 * 异常
	 *
	 * @param msg
	 * @param value
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2018年7月14日
	 */
	private static ParamRuntimeException buildException(String msg, Object value){
		return buildException(msg + "【"+value+"】");
	}
}
